#include "led.h"
#include <esp_log.h>
#include "driver/ledc.h"


#define TAG "Led"

Led::Led(gpio_num_t pin, bool invert_out)
        : m_led(pin), m_invert_out(invert_out)
{
    if(pin == GPIO_NUM_NC) {
        ESP_LOGE(TAG, "Led not connect, pin=%d", pin);
        return;
    }

#if CONFIG_EN_LUM_JUSTIFY
    
    // 配置定时器
    ledc_timer_config_t timer_config = {
        .speed_mode = LEDC_LOW_SPEED_MODE,      // esp32c3必须为该值
        .duty_resolution = LEDC_TIMER_12_BIT,    // 分辨率
        .timer_num = LEDC_TIMER_0,              // 使用定时器0作为LED控制器
        .freq_hz = 100,                        // 频率
        .clk_cfg = LEDC_AUTO_CLK,               // 自动选择时钟源（配置定时器时，其时钟源必须相同）
        .deconfigure = false                    //
    };
    ESP_ERROR_CHECK(ledc_timer_config(&timer_config));

    // 配置通道
    ledc_channel_config_t channel_config = {
        .gpio_num = static_cast<int>(m_led),
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .channel = LEDC_CHANNEL_0,
        .intr_type = LEDC_INTR_DISABLE,             // 渐变完成不产生中断
        .timer_sel = LEDC_TIMER_0,
        .duty = 5,
        .hpoint = 0,
        .sleep_mode =  LEDC_SLEEP_MODE_NO_ALIVE_NO_PD,
        .flags = {
            .output_invert = m_invert_out ? 1U : 0U
        }
    };
    ESP_ERROR_CHECK(ledc_channel_config(&channel_config));

#else
    gpio_config_t led_conf = {
        .pin_bit_mask = BIT64(m_led),
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE
    };
    ESP_ERROR_CHECK(gpio_config(&led_conf));
    ESP_ERROR_CHECK(gpio_set_direction(m_led, GPIO_MODE_OUTPUT));
    if(m_invert_out == false) {
        gpio_set_level(m_led, 0);
    } else {
        gpio_set_level(m_led, 1);
    }
#endif

    esp_timer_create_args_t blink_timer_args = {
        .callback = [](void *arg) {
            auto led = static_cast<Led*>(arg);
            led->OnBlinkTimer();
        },
        .arg = this,
        .dispatch_method = ESP_TIMER_TASK,
        .name = "Blink Timer",
        .skip_unhandled_events = true,
    };
    ESP_ERROR_CHECK(esp_timer_create(&blink_timer_args, &m_blink_timer));
}

Led::~Led()
{
    esp_timer_stop(m_blink_timer);
}



void Led::TurnOff(void)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    esp_timer_stop(m_blink_timer);
#if CONFIG_EN_LUM_JUSTIFY
    ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0);
#else
    if(m_invert_out == false) {
        gpio_set_level(m_led, 0);
    } else {
        gpio_set_level(m_led, 1);
    }
#endif
}

void Led::TurnOn(void)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    esp_timer_stop(m_blink_timer);
#if CONFIG_EN_LUM_JUSTIFY
    ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
#else
    if(m_invert_out == false) {
        gpio_set_level(m_led, 1);
    } else {
        gpio_set_level(m_led, 0);
    }
#endif
}

void Led::BlinkOnce() 
{
    StartBlinkTask(1, 100);
}

void Led::Blink(int times, int interval_ms) 
{
    StartBlinkTask(times, interval_ms);
}

void Led::StartContinuousBlink(int interval_ms) 
{
    StartBlinkTask(BLINK_INFINITE, interval_ms);
}

void Led::StartBlinkTask(int times, int interval_ms) 
{
    std::lock_guard<std::mutex> lock(m_mutex);
    if(m_invert_out == false) {
        gpio_set_level(m_led, 0);
    } else {
        gpio_set_level(m_led, 1);
    }
    m_blink_counter = times << 1;
    m_blink_interval_ms = interval_ms;
    esp_timer_start_periodic(m_blink_timer, m_blink_interval_ms * 1000);
}

void Led::OnBlinkTimer(void)
{
    std::lock_guard<std::mutex> lock(this->m_mutex);
    this->m_blink_counter--;
    if(this->m_blink_counter & 1) {
#if CONFIG_EN_LUM_JUSTIFY
        ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
#else
        if(m_invert_out == false) {
            gpio_set_level(m_led, 1);
        } else {
            gpio_set_level(m_led, 0);
        }
#endif
    } else {
#if CONFIG_EN_LUM_JUSTIFY
    ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0);
#else
        if(m_invert_out == false) {
            gpio_set_level(m_led, 0);
        } else {
            gpio_set_level(m_led, 1);
        }
        if(this->m_blink_counter == 0) {
            esp_timer_stop(this->m_blink_timer);
        }
#endif    
    } 
}